package log

import (
	"bufio"
	"fmt"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
)

// logrus + lfshook + file-rotatelogs 实现日志框架
type LogConfig struct {
	Level           string        `json:"level"`           //日志等级
	FileName        string        `json:"fileName"`        //日志文件名
	ToConsole       bool          `json:"toConsole"`       //
	FileDiffByLevel bool          `json:"fileDiffByLevel"` //
	RotateTime      time.Duration `json:"rotateTime"`      //
	MaxAge          time.Duration `json:"maxAge"`          //
	SyncLog         bool          `json:"syncLog"`         //
	MaxBytesPerHour int           `json:"maxBytesPerHour"` //
	Service         string        `json:"service"`         //
}

// 动态调整日志级别,收到SIGUSR1信号增大日志级别直到TraceLevel(5)，SIGUSR2减小日志级别直到PanicLevel(0)
func handleSignal() {
	signalChan := make(chan os.Signal, 3)
	signal.Notify(signalChan, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM)
	for {
		sig := <-signalChan
		logrusLevel := logrus.GetLevel()
		switch sig {
		case syscall.SIGUSR1:
			switch logrusLevel {
			case logrus.TraceLevel:
				return
			default:
				logrus.SetLevel(logrusLevel + 1)
			}
		case syscall.SIGUSR2:
			switch logrusLevel {
			case logrus.PanicLevel:
				return
			default:
				logrus.SetLevel(logrusLevel - 1)
			}
		case syscall.SIGTERM:
			logrus.Info("Get signal SIGTERM, will exit with code 0")
			os.Exit(0)
		}
	}
}

func InitLog(config LogConfig) error {
	if config.FileName == "" {
		config.FileName = "frame.log"
	}
	if config.MaxBytesPerHour == 0 { //不填默认1个G
		config.MaxBytesPerHour = 1024 * 1024 * 1024
	}
	/*
		ToConsole控制输出到stdout还是日志文件，false表示输出到日志文件，true表示输出到控制台
		输出到控制台的日志格式为下面的consoleFormatter是个text格式，形如：
			INFO[2022-04-25T10:40:27+08:00]log_test.go:24 infra-log.TestSimpleLog() info log
		到日志文件格式则为logrusFormatter是个json格式，形如：
		    {"file":"log_test.go:24","func":"infra-log.TestSimpleLog()","level":"info","msg":"info log","time":"2022-04-25T15:52:56+08:00"}
	*/
	if !config.ToConsole {
		devNull, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
		if err != nil {
			logrus.Println("failed to disable console output", err)
		} else {
			writer := bufio.NewWriter(devNull)
			logrus.SetOutput(writer)
		}
	}
	s, err := os.Stat(filepath.Dir(config.FileName))
	if err != nil && os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Dir(config.FileName), 0755); err != nil {
			return err
		}
	} else {
		if !s.IsDir() {
			return fmt.Errorf("path %s is not dir", filepath.Dir(config.FileName))
		}
	}

	if config.RotateTime == 0 {
		config.RotateTime = time.Hour
	}

	if config.MaxAge == 0 {
		config.MaxAge = 24 * time.Hour * 7
	}

	if config.Level != "" {
		confLevel, err := logrus.ParseLevel(config.Level)
		if err != nil {
			logrus.SetLevel(confLevel)
		}
	} else {
		logrus.SetLevel(logrus.InfoLevel)
	}

	//去除扩展名，更换为.log，以便于线上日志的压缩
	infoLogFile := strings.SplitN(config.FileName, ".", 2)[0]
	logFileSuffix := ".log"
	infoLinkName := infoLogFile + logFileSuffix
	//做日志切割
	infoWriter, err := rotatelogs.New(
		infoLogFile+".%Y_%m_%d-%H_%M_%S"+logFileSuffix,
		rotatelogs.WithLinkName(infoLinkName),
		rotatelogs.WithMaxAge(config.MaxAge),
		rotatelogs.WithRotationTime(config.RotateTime),
	)
	if err != nil {
		return err
	}
	debugWriter := infoWriter
	otherWriter := infoWriter
	traceLogFile := infoLogFile + ".tracing"
	debugLinkName := traceLogFile + logFileSuffix
	traceWriter, _ := rotatelogs.New(
		traceLogFile+".%Y-%m-%d-%H_%M_%S"+logFileSuffix,
		rotatelogs.WithLinkName(debugLinkName),
		rotatelogs.WithMaxAge(config.MaxAge),
		rotatelogs.WithRotationTime(config.RotateTime),
	)
	if config.FileDiffByLevel {
		debugLogFile := infoLogFile + ".debug"
		debugLinkName := debugLogFile + logFileSuffix
		debugWriter, err = rotatelogs.New(
			debugLogFile+".%Y-%m-%d-%H_%M_%S"+logFileSuffix,
			rotatelogs.WithLinkName(debugLinkName),
			rotatelogs.WithMaxAge(config.MaxAge),
			rotatelogs.WithRotationTime(config.RotateTime),
		)
		if err != nil {
			return err
		}
		otherLogFile := infoLogFile + ".warn_err_fatal_panic"
		otherLinkName := otherLogFile + logFileSuffix
		otherWriter, err = rotatelogs.New(
			otherLogFile+".%Y-%m-%d-%H_%M_%S"+logFileSuffix,
			rotatelogs.WithLinkName(otherLinkName),
			rotatelogs.WithMaxAge(config.MaxAge),
			rotatelogs.WithRotationTime(config.RotateTime),
		)
		if err != nil {
			return err
		}
	}
	logrus.SetReportCaller(true)
	logrusFormatter := logrus.JSONFormatter{
		TimestampFormat: time.RFC3339,
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)

			// 这里去掉了包名, 只打方法名
			functionNames := strings.Split(f.Function, "/")
			return fmt.Sprintf("%s()", functionNames[len(functionNames)-1]), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}
	consoleFormatter := logrus.TextFormatter{
		ForceColors:     true,
		FullTimestamp:   true,
		TimestampFormat: time.RFC3339,
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)

			// 这里去掉了包名, 只打方法名
			functionNames := strings.Split(f.Function, "/")
			return fmt.Sprintf("%s()", functionNames[len(functionNames)-1]), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}
	logrus.SetFormatter(&consoleFormatter)
	//7个日志级别，依次是Trace << Debug << Info << Warning << Error << Fatal << Panic
	var hook logrus.Hook = lfshook.NewHook(lfshook.WriterMap{
		logrus.TraceLevel: traceWriter, //不同级别设置不同输出目的
		logrus.DebugLevel: debugWriter,
		logrus.InfoLevel:  infoWriter,
		logrus.WarnLevel:  otherWriter,
		logrus.ErrorLevel: otherWriter,
		logrus.FatalLevel: otherWriter,
		logrus.PanicLevel: otherWriter,
	}, &logrusFormatter)
	//新增hook
	logrus.AddHook(hook)
	go handleSignal()
	return nil
}
